---
sidebar_label: TopoJSON Map
title: TopoJSON Map
description: Learn how to configure a TopoJSON Map
---
import React from 'react'
import BrowserOnly from '@docusaurus/BrowserOnly'
import CodeBlock from '@theme/CodeBlock'
import { PropsTable } from '@site/src/components/PropsTable'
import { DocWrapper, InputWrapper } from '../wrappers'
import { data, pointData, heatmapData } from './data'

export const topoJSONMapProps = () => ({
  name: "TopoJSONMap",
  configKey: "component",
  containerName: "SingleContainer",
  topojson: "WorldMapTopoJSON",
  height: 400,
  data: {},
  dataType: "MapPoint,MapLink,MapArea",
})

export const wiki = (url) => <a href={`https://en.wikipedia.org/wiki/${url}`}>{link}</a>

export const TopoDoc = (props) => (
  <BrowserOnly>
  {() =>  {
      const lib = require('@unovis/ts/maps')
      const imports = props.showContext ? {
        "@unovis/ts/maps": [props.topojson],
        "@unovis/ts": ["MapData"]
      } : undefined
      return (
        <DocWrapper { ...props } imports={imports} hiddenProps={{ topojson: lib[props.topojson] }}/>
      )
    }}
  </BrowserOnly>
)

The _TopoJSONMap_ component is a map that is renders topological geo-data from the
[TopoJSON](https://github.com/topojson/topojson) data format. You can provide your own custom _topojson_
or use one of the [pre-configured topojson files](#topojson-configuration) provided in `@unovis/ts/maps`.
In addition to custom topologies, _TopoJSONMap_ supports a variety of features including the
ability to add points and links, feature customization, zooming, alternate map projections and more.

## Basic Configuration
<TopoDoc
  {...topoJSONMapProps()}
  showContext="full"
/>

## Map Data
There are three main building blocks that can make up the data supplied to _TopoJSONMap_:
* **point**, a geographical coordinate
* **link**, a graph edge that connects two _points_
* **area**, a geographical area

The container for _TopoJSONMap_ accepts data in the form of an `Object` with these three building
blocks as keys mapped to their data arrays, like so:

```ts
type MapData = {
  points: MapPoint[];
  links: MapLink[];
  areas: MapArea[];
}
```

where [`MapPoint`](#points), [`MapLink`](#links) and [`MapArea`](#areas), are custom types that represent map data. Keep
reading to learn more about the minimum configurations for these types.

## Points
A minimum viable datatype of the objects provided to the `points` attribute is:

```ts
type MapPoint = {
  id: string;
  latitude: number;
  longitude: number;
  color: string;
}
```

You can also provide these values through the `latitude`, `longitude`, `pointId`, and `color` properties.

### `mapFitToPoints`
Enabling `mapFitToPoints` will automatically adjust the map's zoom level to fit all the points provided.

<>{Array.from([false, true], fit => (<>
  <h4>{fit ? 'After' : 'Before'}: </h4>
  <TopoDoc
    key={`fit${fit}`}
    {...topoJSONMapProps()}
    height={200}
    data={{ points: [
      { id: 'NYC', latitude: 40.7128, longitude: -74.0060 },
    { id: 'LAX', latitude: 34.0522, longitude: -118.2437 },
      { id: 'LON', latitude: 51.5074, longitude: -0.1278 },
    ]}}
    disableZoom
    mapFitToPoints={fit}
    excludeTabs
  />
</>))}</>

### Point Styling
The following _TopoJSONMap_ properties accept accessor functions to customize the appearance of your
points:
* `pointColor`
* `pointCursor`
* `pointRadius`
* `pointStrokeWidth`

### Point Labels
You can provide labels with the `pointLabel` attribute, which accepts a `StringAccessor` function to
be called on each `MapPoint` datum.

For example, consider the following type and accessor function:
```ts
type MapPoint = {
  id: string;
  latitude: number;
  longitude: number;
  city: string;
}

const pointLabel = (d: MapPoint) => d.city
```

<TopoDoc
  {...topoJSONMapProps()}
  hideTabLabels
  data={{ points: data.points }}
  pointLabel={d => d.city}/>

### Point Label Styling
The following _TopoJSONMap_ properties can further customize your Point Label.
* `pointLabelPosition`
* `pointLabelTextBrighnessRatio`

### Example: Custom Points
export const customData = ({
  points: data.points.map(d => ({
    ...d,
    serverName: `server-${d.city.substring(0,1)}${Math.floor(Math.random() * 10)}`,
    userCount: Math.floor(Math.random() * 80) + 20,
    threatLevel: Math.floor(Math.random() * 4)
  }))
});
export const colorLegendItems = [
  { name: 'Safe', color: 'green' },
  { name: 'Mild', color: 'yellow' },
  { name: 'Moderate', color: 'orange'},
  { name: 'Severe', color: 'red' }
]

<BrowserOnly>
{() => {
  const { TopoJSONMap } = require('@unovis/ts')
  return (
    <TopoDoc
      {...topoJSONMapProps()}
      data={customData}
      mapFitToPoints={true}
      pointLabel={d => d.serverName}
      pointRadius={d => d.userCount / 10}
      pointColor={d => ['green', 'yellow', 'orange', 'red'][d.threatLevel]}
      showContext="minimal"
      components={[
        {name: "BulletLegend", props: { items: colorLegendItems }, key: 'components'},
        {
          name: "Tooltip",
          key: "tooltip",
          props: {
            triggers: {
              [TopoJSONMap.selectors.pointCircle]: d => `Users: ${d.userCount}`
            }
          }
        }
      ]}/>
  )
}}
</BrowserOnly>

## Links
A minimum viable data type of the objects provided to the `links` attribute contains the keys
`source` and `target`, which correspond to the points the link will connect. Most commonly, the
`pointId`.

```ts
type MapLink = {
  source: string | number | MapPoint;
  target: string | number | MapPoint;
}
```
Or, you can simply provide these values through the `linkSource` and `linkTarget` properties.

<TopoDoc
  {...topoJSONMapProps()}
  data={{...data, areas: [] }}
  excludeTabs
/>

#### Link customization
You can further customize the map _Links_ with the following properties:
* `linkColor`
* `linkCursor`
* `linkWidth`

## Areas
To work with features in the _TopoJSONMap_, all you need is a unique `id` which is defined in the
chart's _topojson_ definition or an `areaId` accessor function. For example, in our `WorldMapTopoJSON`
_topojson_, every country has a unique id that corresponds to the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
country code. See our [topojson configuration](#topojson-configuration) section for more details.

```ts
type MapArea = {
  id: string;
}
```

As a basic example, let's say you have an array of countries created from ISO codes:

```ts
const countryCodes = [
  'AU','BR','CN','EG','FR','IN','JP','MX','NO','PE','PH','RU','TZ','US'
]
const areaData = countryCodes.map(id => ({ id }))
const data = { areas: areaData }
```

The provided countries will be highlighted according to their color defined in the topojson file:

<TopoDoc
  {...topoJSONMapProps()}
  data={{ areas: data.points }}
  areaId={d => d.id.substring(0, 2)}
  excludeTabs
/>

### Custom Color
You can override the default area colors by including a `color` property in `AreaDatum` or by providing
an `areaColor` accessor function.

#### Method 1: Through `AreaDatum` property

```ts
const data = {
  points: [],
  links: [],
  areas: [
    { id: 'AU', color: 'red' },
    { id: 'BR', color: 'blue' },
    { id: 'CN', color: 'green' },
  ]
}
```

#### Method 2: Through `areaColor` accessor function

```ts
const areaColor = (d: AreaDatum) => {
  switch (d.id) {
    case 'AU': return 'red'
    case 'BR': return 'blue'
    case 'CN': return 'green'
  }
}
```

The result:
<TopoDoc
  {...topoJSONMapProps()}
  data={{ areas:  [
  { id: 'AU', color: 'red', name: 'Australia' },
  { id: 'BR', color: 'blue', name: 'Brazil' },
  { id: 'CN', color: 'green', name: 'China' },
] }}
  areaId={d => d.id}
  excludeTabs
/>

**Note**: If `areaColor` is provided, it will override the `color` property in `AreaDatum`.

## Projections
You can provide a projection for your _TopoJSONMap_ with a `MapProjection` instance. See D3's
[geo projections](https://github.com/d3/d3-geo) for more information.

<BrowserOnly>
{() => {
  const { MapProjectionKind, MapProjection } = require('@unovis/ts')
  const { WorldMapTopoJSON } = require('@unovis/ts/maps')
  const imports = {
    "@unovis/ts": ["MapData", "MapProjection"],
    "@unovis/ts/maps": ["WorldMapTopoJSON"],
  }
  const [curr, setCurr] = React.useState(MapProjection.Mercator)
  return (
    <InputWrapper {...topoJSONMapProps()}
      hiddenProps={{ topojson: WorldMapTopoJSON, projection: curr }}
      inputType="select"
      property="projection"
      imports={imports}
      defaultValue="MapProjection.Mercator()"
      options={Object.values(MapProjectionKind)}
      onChange={e => {
        setCurr(MapProjection[e])
        return `MapProjection.${e}()`
      }}
      showContext="minimal"/>
  )}}
</BrowserOnly>

## Zoom
By default, zooming is enabled for a _TopoJSONMap_ component. You can disable it by setting the
`disableZoom` property to `true`.

For further customization, you can configure the following zoom properties:

### `zoomFactor`
To set the initial zoom factor.
<TopoDoc {...topoJSONMapProps()} zoomFactor={5}/>

### `zoomExtent`
`zoomExtent` represents the range `[a,b]` which your map can zoom in and out, where `[a, b]` are the
minimum and maximum zoom factors, respectively.

<TopoDoc {...topoJSONMapProps()} zoomFactor={15} zoomExtent={[10,25]}/>

### `zoomDuration`
`zoomDuration` is the duration of the animation on when zooming in on your _TopoJSONMap_.

## Heatmap Mode
For datasets with a lot of points, you can enable `heatmapMode`
<TopoDoc {...topoJSONMapProps()} data={heatmapData} heatmapMode={true}/>

You can customize the appearance of your heat map blur with the `heatmapModeBlurStdDeviation` property.

To lower or raise the threshold that will disable the blur effect of your heat map, use the the
`heatmapModeZoomLevelThreshold` property. You can provide a zoom level, (i.e. `2` for _2x_ zoom),
that once reached, that will no longer display the blur effect.

## TopoJSON Configuration
In order for the _TopoJSONMap_ component to render properly, the `topojson` property and a
valid `mapFeatureName` must be provided. The `mapFeatureName` corresponds to the type of unique
_area_ ids in your data.

<TopoDoc {...topoJSONMapProps()} topojson="FranceTopoJSON" mapFeatureName="regions" showContext='full'/>

TopoJSONs currently available in `@unovis/ts/maps`:

<table style={{ display: 'table', margin: '0 auto', width: '100%' }}>
  <thead>
      <tr>
        <th><code>topojson</code></th>
        <th><code>mapFeatureName</code></th>
        <th>unique <code>MapArea</code> id</th>
        <th>AreaDatum example</th>
      </tr>
    </thead>
  <tbody>
    {[
      [
        <>WorldMapTopoJSON,<br/>WorldMapSimplestTopoJSON,<br/>WorldMap110mAlpha</>,
        'countries',
        'ISO_3166-1_alpha-2',
        'ISO 2-digit country code',
        'ZM'
      ],
      ['ChinaTopoJSON','provinces','ISO_3166-2:CN','ISO subdivision code (CN)','CN-ZS'],
      ['FranceTopoJSON','regions','ISO_3166-2:FR', 'ISO subdivision code (FR)','FR-94'],
      ['GermanyTopoJSON','regions','ISO_3166-2:DE','ISO subdivision code (DE)','DE-13'],
      ['IndiaTopoJSON','regions','ISO_3166-2:IN','ISO subdivision code (IN)', 'IN-DB'],
      ['UKTopoJSON','regions','ONS_coding_system','UK ONS/GSS geocode', 'E15000002'],
      ['USATopoJSON','states','FIPS_state_code#FIPS_state_codes','FIPS 2-digit state code','51'],
      ['USCountiesTopoJSON','counties','List_of_United_States_FIPS_codes_by_county','FIPS 5-digit county code', '53033']
    ].map(([name, feature, url, link, example], i) => (
      <tr key={`row${i}`}>
        <td>{name}</td>
        <td>{feature}</td>
        <td>
          <a href={`https://en.wikipedia.org/wiki/${url}`}>{link}</a>
        </td>
        <td><code>{`{ id: '${example}' }`}</code></td>
      </tr>
    ))
  }
  </tbody>
</table>

## Events
```ts
import { TopoJSONMap } from '@unovis/ts'

const events = {
  [TopoJSONMap.selectors.point]: {},
  [TopoJSONMap.selectors.feature]: {},
}
```

## CSS Variables
The _TopoJSONMap_ component supports additional styling via CSS variables that you can define for
your visualization container.

<details open>
  <summary>All supported CSS variables and their default values</summary>
  <CodeBlock language="css">
{`--vis-map-feature-color: #dce3eb;
--vis-map-boundary-color: #ffffff;
--vis-map-point-label-text-color-dark: #5b5f6d;
--vis-map-point-label-text-color-light: #fff;
--vis-map-point-label-font-weight: 600;
--vis-map-point-label-font-size: 12px;
--vis-map-point-label-font-family
 
/* Dark Theme */
--vis-dark-map-feature-color: #5b5f6d;
--vis-dark-map-boundary-color: #2a2a2a;
--vis-dark-map-point-label-text-color-dark: #fff;
--vis-dark-map-point-label-text-color-light:#5b5f6d;
`}
  </CodeBlock>
</details>

## Component Props
<PropsTable name="VisTopoJSONMap"/>